Prudent Debt
Let's explore what prudent debt is and how we can handle this type of debt.
What is prudent debt?#
Prudent technical debt can be considered “outdated code.” Code that doesn’t use the latest versions of everything. Code that uses patterns that have fallen out of favor. Code that is slow. Code that has long-standing bugs. Code that is verbose because it supports features and abstractions nobody uses anymore.
“We’re programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We’re not excited by incremental renovation: tinkering, improving, planting flower beds.”
Who is responsible for the prudent debt?#
Juniors loathe this kind of debt. It runs against everything they’ve been taught is the best way to do things, and of course, the best way is the only way.
Technical debt makes money#
Seniors understand that only successful codebases live long enough to become technical debt. They understand this because they have written code that became debt. They have also tried to replace that debt and learned the same lesson everyone learns: Technical debt makes money. Not only does it make money, but it is probably superior to any replacement attempt.
Bugs rely on users#
Old code has been used. It has been tested in production and at scale. Lots of bugs have been found, and they’ve been fixed. By definition, only the noncritical bugs remain! Hyrum’s Law teaches us that all observable behaviors of a program, even bugs, will be relied on by users. Imagine fixing a bug to get complaints to undo your fix!
Optimize for inevitable change#
Sometimes, you should intentionally load up debt because you don’t yet know the problem space well. Juniors rush to keep things DRY, but this causes hasty abstractions that they regret and have to unwind later. Seniors understand that code is copied, read, moved, and deleted, far more than it is written. Instead of optimizing for conciseness or cleverness, it is better to optimize for inevitable change.
Tip: For a great visualization of this, see Dan Abramov’s Deconstruct 2019 talk.
Senior developers wrangle technical debt#
As a senior, you’re paid to wrangle technical debt. That’s why some of the highest paying jobs deal with huge legacy systems, whereas enthusiasts and hobbyists will hack on cutting-edge technology for free. Although, it’s not like you can let debt build up forever. You need to be able to figure out how much is too much and how to pay it down while still delivering business objectives.
Wrap dependencies into custom abstractions#
The Strangler or Facade Pattern is most often mentioned as part of the technical debt management toolkit. It is helpful for swapping out underlying implementations while running in place. A good variant of this is the bridge pattern, where you abstract overall third-party dependencies instead of only when upgrading.
“One of my favorite software development practices is to wrap dependencies into custom abstractions. I hate it when a third-party leak all over my code and a refactor takes hours (if not days) because of it.”
- Sarah Dayan
Running migrations#
However, the core skill at hand (especially at the level of systems, not code) is running migrations. This is especially true at high growth startups, where code becomes outdated as a direct and frequent side effect of growing yet another order of magnitude. Here, the playbook is to derisk, enable, and finish strong.
“Software migrations are a way of life at rapidly growing companies, and increasingly, I think your ability to migrate effectively is the defining constraint for your growth.”
Using feature flags#
When operating at scale, what might be a disadvantage can turn into an advantage, like using your production traffic to gain definitive confidence in your code for example. Most big companies have avoided long-lived branches and converged on using feature flags for this. Here’s Paul Biggar, founder of CircleCI and Darklang:
"One way to reduce accidental complexity in Dark is by solving many different problems with a single solution. Feature flags are our workhorse: replacing local dev environments, git branches, code deployment, and of course, still providing the traditional use-case of the slow, controlled roll-out of new code".
Here’s Dan Abramov on how Facebook does migrations:
We rely extensively on feature flags to enable and disable functionality or to switch between different implementations. So, we might have a v1 and a v2 checked in and (we have a dynamic check that rolls v2 out to) 1% of users and then 50% of users, and we’ll see if there are any regressions in metrics. And then, when we’re confident that the fix actually works and it doesn’t make anything worse, we’ll switch it over to 100% and delete the old implementation."
Feature flags aren’t free, though. Uber discovered a ton of stale flags clogging up its codebase, making it more complex and possibly slowing down builds and tests. The problem was so bad they wrote an internal tool, called Piranha, to detect and clean them up. It found 6601 flags, 17% of which weren’t used. Your flags themselves can be technical debt.
Technical Debt
Reckless Debt